package com.hirsi.module.menu.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.hirsi.HAdminApplicationStarter;
import com.hirsi.core.constant.HttpContextHolder;
import com.hirsi.core.expand.TableRUpdate;
import com.hirsi.core.expand.TreeNode;
import com.hirsi.module.menu.convert.MenuConvert;
import com.hirsi.module.menu.entity.Menu;
import com.hirsi.module.menu.entity.RMenuApi;
import com.hirsi.module.menu.mapper.MenuMapper;
import com.hirsi.module.menu.service.MenuService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hirsi.module.menu.service.RMenuApiService;
import com.hirsi.module.menu.vo.EditMenuVo;
import com.hirsi.module.menu.vo.result.ApiTreeSelectorResult;
import com.hirsi.module.menu.vo.result.MenuTreeResult;
import com.hirsi.module.menu.vo.result.PermissionResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 菜单表(开发环境可维护) 服务实现类
 * </p>
 *
 * @author Hirsi
 * @since 2024-03-12
 */
@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {

    @Autowired
    private RMenuApiService menuApiService;

    private final static List<ApiTreeSelectorResult> apiTreeSelectors = new ArrayList<>();


    @Override
    public PermissionResult permission() {
        List<PermissionResult.MenuPermission> permissionResults = getBaseMapper().selectPermissionResult(HttpContextHolder.getUserId());
        PermissionResult result = new PermissionResult();
        List<PermissionResult.MenuPermission> menuPermissions = permissionResults.stream().filter(item -> item.getType() == 0).collect(Collectors.toList());
        result.setMenuPermissions(TreeNode.toTreeList(menuPermissions));
        List<PermissionResult.MenuPermission> operates = permissionResults.stream().filter(item -> item.getType() == 1).collect(Collectors.toList());
        Map<String, List<PermissionResult.OperatePermission>> operatePermissions = operates.stream().collect(Collectors.groupingBy(k -> menuPermissions.stream().filter(item -> item.getId().equals(k.getPid())).findFirst().get().getUrl(), Collectors.mapping(v -> new PermissionResult.OperatePermission(v.getUrl(), v.getName(), v.getIcon(), v.getShowPosition()), Collectors.toList())));
        result.setOperatePermissions(operatePermissions);
        return result;
    }

    @Override
    public List<MenuTreeResult> tree() {
        List<Menu> menus = lambdaQuery().select(Menu::getId, Menu::getPid, Menu::getName, Menu::getIcon,
                Menu::getType, Menu::getClick, Menu::getShowPosition, Menu::getStatus)
                .orderByAsc(Menu::getSortNum).list();
        List<MenuTreeResult> results = MenuConvert.INSTANCE.menusToMenuTreeResults(menus);
        return TreeNode.toTreeList(results);
    }

    @Override
    public List<ApiTreeSelectorResult> devApiTreeSelectors() {
        return apiTreeSelectors;
    }

    @Override
    public EditMenuVo devEdit(Integer id) {
        Menu menu = lambdaQuery().eq(Menu::getId, id)
                .select(Menu::getId, Menu::getPid, Menu::getName, Menu::getIcon, Menu::getType,
                        Menu::getClick, Menu::getShowPosition, Menu::getStatus).one();
        EditMenuVo vo = MenuConvert.INSTANCE.menuToEditMenuVo(menu);
        List<RMenuApi> menuApis = menuApiService.lambdaQuery().eq(RMenuApi::getMenuId, id)
                .select(RMenuApi::getApiUrl).list();
        if (CollUtil.isNotEmpty(menuApis)) vo.setApiUrls(menuApis.stream().map(RMenuApi::getApiUrl).collect(Collectors.toList()));
        return vo;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void devSave(EditMenuVo vo) {
        Menu menu = MenuConvert.INSTANCE.editMenuVoToMenu(vo);
        menu.insertOrUpdate();
        List<RMenuApi> menuApis = new ArrayList<>();
        if (CollUtil.isNotEmpty(vo.getApiUrls())) vo.getApiUrls().forEach(item -> menuApis.add(new RMenuApi().setMenuId(menu.getId()).setApiUrl(item)));
        List<RMenuApi> oldMenuApiUrls = menuApiService.lambdaQuery().eq(RMenuApi::getMenuId, menu.getId())
                .select(RMenuApi::getId, RMenuApi::getMenuId, RMenuApi::getApiUrl).list();
        TableRUpdate.Result<RMenuApi> execute = TableRUpdate.execute(menuApis, oldMenuApiUrls);
        if (CollUtil.isNotEmpty(execute.getSaves())) menuApiService.saveOrUpdateBatch(execute.getSaves());
        if (CollUtil.isNotEmpty(execute.getDeleteIds())) menuApiService.removeByIds(execute.getDeleteIds());
    }

    @PostConstruct
    public void initApiTreeSelectors() {
        System.out.println("开始初始化接口...");
        Package pck = HAdminApplicationStarter.class.getPackage();
        List<Class<?>> classes = ClassUtil.scanPackage(pck.getName()).stream().filter(item -> item.getAnnotation(Api.class) != null && item .getAnnotation(RequestMapping.class) != null).sorted((o1, o2) -> {
            ApiSupport o1Support = o1.getAnnotation(ApiSupport.class);
            ApiSupport o2Support = o2.getAnnotation(ApiSupport.class);
            int o1Num = 0, o2Num = 0;
            if (o1Support != null) o1Num = o1Support.order();
            if (o2Support != null) o2Num = o2Support.order();
            return o1Num - o2Num;
        }).collect(Collectors.toList());
        List<ApiTreeSelectorResult> apiTreeSelectors = new ArrayList<>();
        for (Class<?> clazz : classes) {
            Api api = clazz.getAnnotation(Api.class);
            RequestMapping classMapping = clazz.getAnnotation(RequestMapping.class);
            Method[] methods = clazz.getMethods();
            if (ArrayUtil.isNotEmpty(methods)) {
                List<Method> methodList = CollUtil.newArrayList(methods);
                methodList.sort((o1, o2) -> {
                    ApiOperationSupport o1Support = o1.getAnnotation(ApiOperationSupport.class);
                    ApiOperationSupport o2Support = o2.getAnnotation(ApiOperationSupport.class);
                    int o1Num = 0, o2Num = 0;
                    if (o1Support != null) o1Num = o1Support.order();
                    if (o2Support != null) o2Num = o2Support.order();
                    return o1Num - o2Num;
                });
                String parentUrl = classMapping.value()[0];
                if (!parentUrl.startsWith("/")) parentUrl = "/" + parentUrl;
                if (parentUrl.endsWith("/")) parentUrl = parentUrl.substring(0, parentUrl.length() - 1);
                ApiTreeSelectorResult parentApiTreeSelector = new ApiTreeSelectorResult()
                        .setUrl(parentUrl).setDescription(api.tags()[0]);
                List<ApiTreeSelectorResult> childApiTreeSelectors = new ArrayList<>();
                for (Method method : methodList) {
                    ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
                    RequestMapping methodMapping = method.getAnnotation(RequestMapping.class);
                    GetMapping getMapping = method.getAnnotation(GetMapping.class);
                    PostMapping postMapping = method.getAnnotation(PostMapping.class);
                    if (apiOperation != null && (methodMapping != null || getMapping != null || postMapping != null)) {
                        String childDescription = apiOperation.value();
                        String[] value = methodMapping != null ? methodMapping.value() : null;
                        if (ArrayUtil.isEmpty(value)) value = getMapping != null ? getMapping.value() : null;
                        if (ArrayUtil.isEmpty(value)) value = postMapping != null ? postMapping.value() : null;
                        String childUrl = ArrayUtil.get(value, 0);
                        if (childUrl.startsWith("/")) childUrl = childUrl.substring(1, childUrl.length());
                        if (childUrl.endsWith("/")) childUrl = childUrl.substring(0, childUrl.length() - 1);
                        childUrl = StrUtil.isNotBlank(parentUrl) ? parentUrl + "/" + childUrl : "/" + childUrl;
                        childApiTreeSelectors.add(new ApiTreeSelectorResult(childUrl, childDescription, null));
                    }
                }
                if (childApiTreeSelectors.size() > 0) apiTreeSelectors.add(parentApiTreeSelector.setChildren(childApiTreeSelectors));
            }
        }
        System.out.println(JSONUtil.toJsonStr(apiTreeSelectors));
    }
}
